using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using TCC.ControleFluxoDeCaixa.Model.Contas;
using TCC.ControleFluxoDeCaixa.Model.Entities;
using TCC.ControleFluxoDeCaixa.Model.Fluxo;
using TCC.ControleFluxoDeCaixa.Model.Gerenciadores;
using TCC.ControleFluxoDeCaixa.Model.Usuarios;

namespace TCC.ControleFluxoDeCaixa.Model.Relatorios
{
    public class RelatorioDeFluxoFinanceiro 
    {
        public SortedSet<DiaFluxo> DiasFluxo { get; private set; }
        public FluxoDeCaixaContextFiltrado Context { get; private set; }
        public decimal SaldoInicial { get; private set; }
        public DateTime? DataInicio { get; set; }
        public DateTime? DataFim { get; set; }
        public HashSet<Categoria> Categorias { get; private set; }
        public HashSet<ContaFinanceira> ContasFinanceiras { get; private set; }
        public HashSet<LancamentoRecorrente> LancamentosRecorrentes { get; private set; }

        public IEnumerable<DiaFluxo> DiasFluxoComVariacao
        {
            get { return DiasFluxo.Where(f=> f.ItensFluxo.Any()).ToArray(); }
        }

        public decimal MenorSaldo
        {
            get
            {
                return DiasFluxo.Count != 0 ? Math.Min(DiasFluxo.Min(c => c.Saldo), 0) : 0;
            }
        }
        public decimal MaiorSaldo
        {
            get { return DiasFluxo.Max(c => c.Saldo); }
        }

        public RelatorioDeFluxoFinanceiro(FluxoDeCaixaContextFiltrado context)
        {
            Context = context;
            Categorias = new HashSet<Categoria>();
            ContasFinanceiras = new HashSet<ContaFinanceira>();
            LancamentosRecorrentes = new HashSet<LancamentoRecorrente>();
        }

        public void RegerarRelatorio(bool comHistorico = true, bool comProjecao = true, bool incluirDiasSemVariacao = false)
        {
            var itens = new List<ItemProjecao>();
            if (comHistorico)
                itens.AddRange(GerarHistorico());
            if (comProjecao)
                itens.AddRange(GerarProjecao());

            AgruparPorDias(itens);

            if (incluirDiasSemVariacao && DiasFluxo.Any())
            {
                var dic = DiasFluxo.ToDictionary(d => d.Dia);
                var min = DiasFluxo.First().Dia.Date;
                var max = DiasFluxo.Max().Dia.Date;
                var lastSaldo = 0m;
                var current = min;
                do
                {
                    if (!dic.ContainsKey(current))
                    {
                        var dia = new DiaFluxo(current, null, lastSaldo);
                        DiasFluxo.Add(dia);
                    }
                    else
                    {
                        lastSaldo = dic[current].Saldo;
                    }
                    current = current.AddDays(1);
                } while (current <= max);

            }
        }
        private IEnumerable<ItemProjecao> GerarProjecao()
        {
            var query = Context.LancamentosRecorrentes.AsQueryable();

            if (ContasFinanceiras.Any())
            {
                var contasIds = ContasFinanceiras.Select(c => c.ContaFinanceiraId).ToArray();
                query = query.Where(l => contasIds.Contains(l.Conta.ContaFinanceiraId));
            }
            if (Categorias.Any())
            {
                var categoriasIds = Categorias.Select(c => c.CategoriaId).ToArray();
                query = query.Where(l => categoriasIds.Contains(l.Categoria.CategoriaId));
            }

            if (LancamentosRecorrentes.Any())
            {
                var lancamentosRecorrentesIds = LancamentosRecorrentes.Select(c => c.LancamentoRecorrenteId).ToArray();
                query = query.Where(l => lancamentosRecorrentesIds.Contains(l.LancamentoRecorrenteId));
            }
            var usuarioId = AutenticadorDeSessao.GetInstance().CurrentSession.Usuario.UsuarioId;
            query = query.Where(l => l.Conta.Usuario.UsuarioId == usuarioId);
            var lancamentos = query.Include(q=> q.Lancamentos).ToArray();
            var now = DateTime.Now.Date;

            return lancamentos.SelectMany(l => l.GetVencimentosNaoPagos().Where(v=> v >= now && v <= DataFim)
                .Select(m => new ItemProjecao(TipoItemDeFluxo.Projetivo,  l.Conta, l.Categoria, m.Date, l.ValorEsperado, l.Nome))).ToArray();

        }
        private IEnumerable<ItemProjecao> GerarHistorico()
        {
            var usuarioId = AutenticadorDeSessao.GetInstance().CurrentSession.Usuario.UsuarioId;
            var query = Context.Movimentos.Where(m => m.Conta.Usuario.UsuarioId == usuarioId).AsQueryable();
            CorrigiDataInicioEFim(query);
            CalculaSaldoInicial(query);

            var q2 = RealizarFiltro(query).Select(m =>
                new
                {
                    m.Conta,
                    m.Lancamento.Categoria,
                    Data = m.DataDoMovimento,
                    Valor = m.ValorAbsoluto * ((int)m.TipoDeMovimento),
                    m.Historico
                }).ToArray();
            return q2.Select(m => new ItemProjecao(TipoItemDeFluxo.Historico, m.Conta, m.Categoria, m.Data, m.Valor, m.Historico)).ToArray();

        }

        private IQueryable<Movimento> RealizarFiltro(IQueryable<Movimento> query)
        {
            var querySaldoAcumulado = query.Where(m => m.DataDoMovimento >= DataInicio && m.DataDoMovimento <= DataFim);
            
            if (Categorias.Any())
            {
                var catIds = Categorias.Select(c => c.CategoriaId).ToArray();
                querySaldoAcumulado = querySaldoAcumulado.Where(m => catIds.Contains(m.Lancamento.Categoria.CategoriaId));
            }
            if (ContasFinanceiras.Any())
            {
                var contaIds = ContasFinanceiras.Select(c => c.ContaFinanceiraId).ToArray();
                querySaldoAcumulado = querySaldoAcumulado.Where(m => contaIds.Contains(m.Conta.ContaFinanceiraId));
            }
            return querySaldoAcumulado;
        }

        private void CorrigiDataInicioEFim(IQueryable<Movimento> query)
        {
            if (DataInicio == null)
            {
                DataInicio = query.Select(m => (DateTime?)m.DataDoMovimento).Min() ?? DateTime.Now.Date;
            }
            if (DataFim == null)
            {
                DataFim = (query.Select(m => (DateTime?)m.DataDoMovimento).Max() ??  DateTime.Now.Date).AddYears(2);
            }
        }
        private void CalculaSaldoInicial(IQueryable<Movimento> query)
        {
            SaldoInicial =
                query.Where(m => m.DataDoMovimento < DataInicio)
                    .Sum(m => (decimal?)(m.ValorAbsoluto* (int) m.TipoDeMovimento)) ?? 0;
        }
        

        private void AgruparPorDias(IEnumerable<ItemProjecao> itens)
        {
            DiasFluxo = new SortedSet<DiaFluxo>();
            var saldoAnterior = SaldoInicial;

            foreach (var grupo in itens.GroupBy(i => i.DataFluxo).OrderBy(g => g.Key))
            {
                var d = new DiaFluxo(grupo.Key, grupo, saldoAnterior);
                saldoAnterior = d.Saldo;
                DiasFluxo.Add(d);
            }
        }
    }
}